﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ST.Core.Util
{
    public class IdWorker
    {
        // 定义常量
        private const long Epoch = 1735689600000L; // 起始时间戳，2025-01-01 00:00:00.000
        private const int WorkerIdBits = 5; // 机器ID位数
        private const int DatacenterIdBits = 5; // 数据中心ID位数
        private const int SequenceBits = 12; // 序列号位数
        private const long MaxWorkerId = -1L ^ (-1L << WorkerIdBits); // 最大机器ID
        private const long MaxDatacenterId = -1L ^ (-1L << DatacenterIdBits); // 最大数据中心ID

        private const int WorkerIdShift = SequenceBits; // 机器ID左移位数
        private const int DatacenterIdShift = SequenceBits + WorkerIdBits; // 数据中心ID左移位数
        private const int TimestampLeftShift = SequenceBits + WorkerIdBits + DatacenterIdBits; // 时间戳左移位数
        private const long SequenceMask = -1L ^ (-1L << SequenceBits); // 序列号掩码

        private long _sequence = 0L; // 序列号
        private long _lastTimestamp = -1L; // 上次生成ID的时间戳

        private readonly long _workerId; // 机器ID
        private readonly long _datacenterId; // 数据中心ID

        private static readonly object _lock = new object();

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="workerId">机器ID</param>
        /// <param name="datacenterId">数据中心ID</param>
        public IdWorker(long workerId, long datacenterId)
        {
            if (workerId > MaxWorkerId || workerId < 0)
                throw new ArgumentException($"worker Id can't be greater than {MaxWorkerId} or less than 0");

            if (datacenterId > MaxDatacenterId || datacenterId < 0)
                throw new ArgumentException($"datacenter Id can't be greater than {MaxDatacenterId} or less than 0");

            _workerId = workerId;
            _datacenterId = datacenterId;
        }

        /// <summary>
        /// 生成下一个ID
        /// </summary>
        /// <returns>返回一个唯一的long型ID</returns>
        public long NextId()
        {
            lock (_lock)
            {
                long timestamp = TimeGen();

                if (timestamp < _lastTimestamp)
                    throw new Exception($"Clock moved backwards. Refusing to generate id for {_lastTimestamp - timestamp} milliseconds");

                if (_lastTimestamp == timestamp)
                {
                    _sequence = (_sequence + 1) & SequenceMask;
                    if (_sequence == 0)
                    {
                        timestamp = TilNextMillis(_lastTimestamp);
                    }
                }
                else
                {
                    _sequence = 0L;
                }

                _lastTimestamp = timestamp;

                return ((timestamp - Epoch) << TimestampLeftShift) |
                    (_datacenterId << DatacenterIdShift) |
                    (_workerId << WorkerIdShift) |
                    _sequence;
            }
        }

        /// <summary>
        /// 获取当前时间戳
        /// </summary>
        /// <returns>当前时间戳</returns>
        private long TimeGen()
        {
            return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds;
        }

        /// <summary>
        /// 等待下一个毫秒
        /// </summary>
        /// <param name="lastTimestamp">上次时间戳</param>
        /// <returns>下一个毫秒的时间戳</returns>
        private long TilNextMillis(long lastTimestamp)
        {
            long timestamp = TimeGen();
            while (timestamp <= lastTimestamp)
            {
                timestamp = TimeGen();
            }
            return timestamp;
        }

        /// <summary>
        /// 将日期转换为ID，仅保留时间戳部分
        /// </summary>
        /// <param name="dateTime">要转换的日期时间</param>
        /// <returns>仅包含时间戳部分的ID</returns>
        public long ConvertDateTimeToId(DateTime dateTime)
        {
            // 将日期转换为UTC时间
            DateTime utcTime = dateTime.ToUniversalTime();
            // 计算与Epoch的时间差（毫秒）
            long timestamp = (long)(utcTime - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds;
            // 将时间戳左移，其他位保持为0
            return (timestamp - Epoch) << TimestampLeftShift;
        }

        /// <summary>
        /// 将ID转换为时间
        /// </summary>
        /// <param name="id">要转换的ID</param>
        /// <returns>转换后的时间</returns>
        public DateTime ConvertIdToDateTime(long id)
        {
            // 将ID右移，获取时间戳部分
            long timestamp = (id >> TimestampLeftShift) + Epoch;
            // 将时间戳转换为DateTime
            DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
            return epoch.AddMilliseconds(timestamp).ToLocalTime();
        }
    }
}
